package com.leyou.auth.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.leyou.auth.config.JwtProperties;
import com.leyou.auth.mapper.ApplicationInfoMapper;
import com.leyou.auth.pojo.ApplicationInfo;
import com.leyou.common.auth.pojo.AppInfo;
import com.leyou.common.auth.pojo.Payload;
import com.leyou.common.auth.pojo.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.exception.pojo.ExceptionEnum;
import com.leyou.common.exception.pojo.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.client.UserClient;
import com.leyou.user.pojo.User;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 授权业务
 */
@Service
public class AuthService {
    @Autowired
    private UserClient userClient;
    @Autowired
    private JwtProperties jwtProps;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ApplicationInfoMapper applicationInfoMapper;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;


    public void login(String username, String password, HttpServletResponse response) {
        //1. 判断用户名和密码是否正确
        User loginUser = userClient.findUserByNameAndPwd(username, password);

        UserInfo userInfo = new UserInfo(loginUser.getId(), loginUser.getUsername(), "admin");

        //2.生成token，并把token写出到response中
        this.generateTokenAndWriteCookie(userInfo, response);
    }

    /**
     * 生成token，并把token写出到response中
     */
    public void generateTokenAndWriteCookie(UserInfo info, HttpServletResponse response) {
        //2.利用JwtUtils+私钥生成加密token
        String token = JwtUtils.generateTokenExpireInMinutes(info, jwtProps.getPrivateKey(), jwtProps.getCookie().getExpire());

        //3）把token写入Cookie对象中
        CookieUtils.newCookieBuilder()
                .response(response)
                .name(jwtProps.getCookie().getCookieName())
                .value(token)
                .domain(jwtProps.getCookie().getCookieDomain())
                .build();
    }

    public UserInfo verify(HttpServletRequest request, HttpServletResponse response) {
        //1.从request取出cookie的token
        String token = CookieUtils.getCookieValue(request, jwtProps.getCookie().getCookieName());

        //2. 验证token是否合法
        Payload<UserInfo> payload = null;

        try {
            payload = JwtUtils.getInfoFromToken(token, jwtProps.getPublicKey(), UserInfo.class);
        } catch (Exception e) {
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }

        /**
         * 判断当前登录用户token是否在黑名单中，如果在的话，报出异常
         */
        if (redisTemplate.hasKey(payload.getId())) {
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }


        //3. 取出token中的载荷UserInfo数据
        UserInfo info = payload.getInfo();


        //刷新token过期时间
        //1.取出当前token的过期时间
        Date expireTime = payload.getExpiration();

        //2.计算刷新时间=过期时间-15
        DateTime refreshTime = new DateTime(expireTime).minusMinutes(jwtProps.getCookie().getRefreshTime());

        //3.判断刷新时间<当前时间，重新生成token
        //isBeforeNow(): 判断指定时间是否在当前时间之前
        if (refreshTime.isBeforeNow()) {
            //把当前token加入黑名单
            addToBlackList(token);

            //重新生成token
            generateTokenAndWriteCookie(info, response);
        }

        //4. 返回
        return info;
    }

    public void logout(HttpServletRequest request, HttpServletResponse response) {
        String token = CookieUtils.getCookieValue(request, jwtProps.getCookie().getCookieName());

        //1.删除浏览器的Cookie
        CookieUtils.deleteCookie(
                jwtProps.getCookie().getCookieName(),
                jwtProps.getCookie().getCookieDomain(),
                response);

        //2.把Cookie中的token加入黑名单（存入redis中）
        addToBlackList(token);
    }

    /**
     * 把token存入redis的黑名单
     *
     * @param token
     */
    private void addToBlackList(String token) {
        //1.取出token的id
        Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, jwtProps.getPublicKey(), UserInfo.class);
        String tokenId = payload.getId();

        //2.取出token的过期时间
        Date expireTime = payload.getExpiration();

        //3.计算剩余有效时间毫秒值
        long remainTime = expireTime.getTime() - System.currentTimeMillis();

        //2.存入redis
        redisTemplate.opsForValue().set(tokenId, "1", remainTime, TimeUnit.MILLISECONDS);
    }


    /**
     * 判断服务名称和服务密钥是否合法
     */
    public ApplicationInfo checkApplicationByNameAndSecret(String serviceName, String secret) {
        //1.先判断服务名称是否存在

        ApplicationInfo info = new ApplicationInfo();
        info.setServiceName(serviceName);
        QueryWrapper<ApplicationInfo> queryWrapper = Wrappers.query(info);
        info = applicationInfoMapper.selectOne(queryWrapper);

        if (info == null) {
            throw new LyException(ExceptionEnum.INVALID_SERVER_ID_SECRET);
        }

        //2.再判断服务密钥是否正确
        if (!passwordEncoder.matches(secret, info.getSecret())) {
            throw new LyException(ExceptionEnum.INVALID_SERVER_ID_SECRET);
        }
        return info;
    }

    /**
     * 提供给微服务申请服务token的方法
     */
    public String authorization(String serviceName, String secret) {

        //1.检查服务名称和密钥是否正确
        ApplicationInfo info = this.checkApplicationByNameAndSecret(serviceName, secret);

        //2.封装AppInfo对象
        List<String> targetList = applicationInfoMapper.findTargetListByServiceName(serviceName);

        AppInfo appInfo = new AppInfo(info.getId(), info.getServiceName(), targetList);

        //3.生成token并返回
        String token = JwtUtils.generateTokenExpireInMinutes(appInfo, jwtProps.getPrivateKey(), jwtProps.getApp().getExpire());
        return token;
    }


}
